local screen = {
    width = 0, height = 0,
    row_off = 0, col_off = 0,
    cursor_x = 1, cursor_y = 1
}
local config = require "config"

-- ansi code
local ANSI_CLEAR_SCREEN    = "\x1b[2J"
local ANSI_HIDE_CURSOR     = "\x1b[?25l"
local ANSI_SHOW_CURSOR     = "\x1b[?25h"
local ANSI_STORE_CURSOR    = "\x1b[s"
local ANSI_RESTORE_CURSOR  = "\x1b[u"

-- store the ansi instructions
local ansi_instrs = {}

function screen:new(term, object)
    object = object or {}
    self.__index = self
    setmetatable(object, self)

    local w, h = term:get_terminal_size()
    self.width = w
    self.height = h - 3

    return object
end

function screen:exec_ansi()
    if #ansi_instrs == 0 then return end

    local result = table.concat(ansi_instrs)
    io.write(result)

    -- clear the ansi_instrs
    for k, _ in ipairs(ansi_instrs) do
        ansi_instrs[k] = nil
    end
end

function screen:move_cursor_to(row, col)
    ansi_instrs[#ansi_instrs + 1] = string.format("\x1b[%d;%dH", row, col)
end

function screen:move_cursor_up(text)
    if self.cursor_x == 1 then
        self.row_off = (self.row_off > 0) and (self.row_off - 1) or self.row_off
    else
        self.cursor_x = self.cursor_x - 1
    end

    local line_len = #text[self.cursor_x + self.row_off] + 1
    self.cursor_y = (self.cursor_y > line_len) and line_len or self.cursor_y
end

function screen:move_cursor_down(text)
    if self.cursor_x == self.height then
        self.row_off = (self.cursor_x + self.row_off < #text) and self.row_off + 1 or self.row_off
    else
        self.cursor_x = (self.cursor_x + self.row_off < #text) and self.cursor_x + 1 or self.cursor_x
    end

    local line_len = #text[self.cursor_x + self.row_off] + 1
    self.cursor_y = (self.cursor_y > line_len) and line_len or self.cursor_y
end

function screen:move_cursor_left()
    if self.cursor_y == 1 then
        self.col_off = (self.col_off > 0) and (self.col_off - 1) or self.col_off
    else
        self.cursor_y = self.cursor_y - 1
    end
end

function screen:move_cursor_right(text)
    local line_len = #text[self.cursor_x + self.row_off] + 1

    if self.cursor_y == self.width then
        self.col_off = (self.cursor_y + self.col_off < line_len) and self.col_off + 1 or self.col_off
    else
        self.cursor_y = (self.cursor_y + self.col_off < line_len) and self.cursor_y + 1 or self.cursor_y
    end
end

function screen:hide_cursor()
    ansi_instrs[#ansi_instrs + 1] = ANSI_HIDE_CURSOR
end

function screen:show_cursor()
    ansi_instrs[#ansi_instrs + 1] = ANSI_SHOW_CURSOR
end

function screen:clear_screen()
    ansi_instrs[#ansi_instrs + 1] = ANSI_CLEAR_SCREEN
end

function screen:store_cursor()
    ansi_instrs[#ansi_instrs + 1] = ANSI_STORE_CURSOR
end

function screen:restore_cursor()
    ansi_instrs[#ansi_instrs + 1] = ANSI_RESTORE_CURSOR
end

function screen:draw_status_bar(text)
    self:move_cursor_to(self.height + 2, 1)
    self:exec_ansi()

    io.write(string.format("%s [%s] %d-%d [%d/%d]",
        (config.filename == "") and "UNNAMED" or config.filename,
        (config.save == false) and "+" or "-", self.cursor_x, self.cursor_y,
        self.cursor_x + self.row_off, #text))
end

function screen:draw_message_bar(text)
    self:move_cursor_to(self.height + 3, 1)
    self:exec_ansi()

    if config.event == "e" then         -- exit
        io.write(string.format("Exit Hexa? [%s] (y/n) : ",
            (config.save == true) and "SAVED" or "UNSAVED"))
        local ch = io.read(1)
        if ch ~= "n" and ch ~= "N" then return 1 end
    elseif config.event == "s" then     -- save
        if config.filename == "" then
            io.write("Saved file name: ")
            local ch = io.read(1)
            while ch ~= "\r" do
                io.write(ch)
                config.filename = config.filename .. ch
                ch = io.read(1)
            end
            if config.filename ~= "" then
                text:save(config.filename)
                config.save = true
            end
        else
            io.write("SAVED")
            text:save(config.filename)
            config.save = true
        end
    end

    config.event = ""
    return 0
end

function screen:flush(text)
    self:store_cursor()
    self:hide_cursor()
    self:clear_screen()
    self:move_cursor_to(1, 1)
    self:exec_ansi()

    text:write_lines(io.stdout, self.row_off + 1, self.col_off + 1, self.height)
    self:draw_status_bar(text)
    local status = self:draw_message_bar(text)

    self:restore_cursor()
    self:show_cursor()
    self:exec_ansi()

    return status
end

return screen